from txosc import dispatch
from Bases import BaseObject

def join_address(*args):
    s = '/'.join(['/'.join(arg.split('/')) for arg in args])
    return s
    
def get_node_path(node):
    parent = node
    path = []
    while parent is not None:
        path.append(parent._name)
        parent = parent._parent
    path.reverse()
    return join_address(*path)

class OSCBaseObject(BaseObject):
    _saved_attributes = ['osc_address']
    def __init__(self, **kwargs):
        self.osc_enabled = False
        address = kwargs.get('osc_address')
        if address is None and hasattr(self, 'osc_address'):
            address = getattr(self, 'osc_address')
        if address is not None:
            self.osc_address = str(address)
        parent = kwargs.get('osc_parent_node')
        if parent is None and hasattr(self, 'osc_parent_node'):
            parent = getattr(self, 'osc_parent_node')
        if parent is not None:
            self.osc_parent_node = parent
        self.osc_enabled = parent is not None and address is not None
        
        if self.osc_enabled:
            self.osc_handlers = {}
            self.osc_child_nodes = set()
            self.osc_node = self.osc_parent_node.add_new_node(name=self.osc_address)
        else:
            self.osc_address = None
            
        super(OSCBaseObject, self).__init__(**kwargs)
            
    def add_osc_child(self, **kwargs):
        if self.osc_enabled:
            address = kwargs.get('address')
            d = {'osc_address':address, 'osc_parent_node':self.osc_node}
            return d
        return {}
        
    def add_osc_handler(self, **kwargs):
        '''Add an OSC handler
        :Parameters:
            'address' : Relative address to self.osc_node. Default is
                        None which will use the address of self.osc_node
            'callbacks' : Dict of {'address':callback} to handle. Default is None
                          which disables callback mode and requires parameters below.
            'object' : Instance object to get and set attributes from OSC messages.
                       Default is None.
            'attr_name' : Name of object's attribute to use.  Default is None.
        '''
        if self.osc_enabled:
            address = kwargs.get('address')
            attr_name = kwargs.get('attr_name', address)
            connect_signals = kwargs.get('connect_signals', True)
            kwargs.setdefault('object', self)
            
            if address is None:
                node = self.osc_node
                address = self.osc_address
                kwargs['address'] = address
            else:
                node = self.osc_node.add_new_node(name=address)
            kwargs.setdefault('osc_node', node)
            
            objhandler = OSCObj(**kwargs)
            
            if connect_signals and not objhandler.callback_mode:
                obj.connect('attr_watch_%s' % (attr_name), objhandler.on_attr_change)
            self.osc_handlers.update({address:objhandler})
            
            return objhandler
    

class OSCObj(BaseObject):
    def __init__(self, **kwargs):
        super(OSCObj, self).__init__()
        self.register_signal('request_value', 'change_value')
        self.address = kwargs.get('address')
        callbacks = kwargs.get('callbacks')
        self.callback_mode = kwargs.get('callback_mode', callbacks is not None)
        if self.callback_mode:
            self.callbacks = callbacks
        else:
            self.object = kwargs.get('object')
            self.attr_name = kwargs.get('attr_name')
            self.attr_lock = False
        self.osc_node = kwargs.get('osc_node')
        self.osc_node.addCallback(self.address+'/*', self.handle_message)
        
    def add_callbacks(self, **kwargs):
        self.callbacks.update(kwargs)
    
    def handle_message(self, message, host_addr):
        address = message.address
        method = address.split('/')[-1:][0]
        print 'received: address=%s, method=%s, args=%s' % (address, method, message.getValues())
        if self.callback_mode:
            if method in self.callbacks:
                self.callbacks[method](method=method, address=address, values=message.getValues())
        else:
            if method == self.address:
                self.send_methods()
            elif method == 'current-value':
                self.send_value(reply=True)
            elif method == 'set-value':
                if len(message.getValues()):
                    value = message.getValues()[0]
                    self.set_value(value=value)
                    
    def send_methods(self):
        pass
        
    def send_value(self, **kwargs):
        value = getattr(self.object, self.attr_name)
        self.emit('request_value', obj=self)
        self.osc_node.send_message(address='set-value', value=value)
        
    def set_value(self, **kwargs):
        value = kwargs.get('value')
        if value != getattr(self.object, self.attr_name):
            self.attr_lock = True
            setattr(self.object, self.attr_name, value)
            
    def request_value(self, *args, **kwargs):
        self.osc_node.send_message(address='current-value')
        
    def on_attr_change(self, **kwargs):
        if not self.attr_lock:
            self.send_value(reply=False)
        self.attr_lock = False

class OSCNode(BaseObject, dispatch.Receiver):
    _saved_class_name = 'OSCNode'
    _saved_child_classes = ['_childNodes']
    def __init__(self, **kwargs):
        #super(OSCNode, self).__init__()
        BaseObject.__init__(self, **kwargs)
        dispatch.Receiver.__init__(self)
        
        if 'name' in kwargs:
            self.setName(kwargs.get('name'))
        if 'parent' in kwargs:
            self.setParent(kwargs.get('parent'))
        self.is_root_node = kwargs.get('root_node', False)
        self.transmit_callback = kwargs.get('transmit_callback')
    def add_new_node(self, **kwargs):
        name = kwargs.get('name')
        parent = kwargs.get('parent', self)
        if parent == self:
            return OSCNode(name=name, parent=self)
        if self._parent is not None:
            return self._parent.add_new_node(**kwargs)
        return None
        
    def send_message(self, **kwargs):
        '''Send an OSC message object up through the tree and finally
        out of the root node
        :Parameters:
            'address' : relative OSC address from the node that is sending
            'value' : OSC args to send, can be list, tuple or single value
                        of any type supported by OSC
        '''
        if self.is_root_node:
            self.transmit_callback(**kwargs)
            return
        address = kwargs.get('address')
        if type(address) == str:
            address = [address]
        address[0:0] = [self._name]
        kwargs['address'] = address
        self._parent.send_message(**kwargs)
        
    def get_full_path(self, address=None):
        if self.is_root_node:
            return address
        if address is None:
            address = []
        address[0:0] = [self._name]
        return self._parent.get_full_path(address)
        
    def addNode(self, name, instance):
        super(OSCNode, self).addNode(name, OSCNode())
